/*
 * Copyright (c) 2009, Jan Pastor. All Rights Reserved.
 */

package sk.jp.jitag.ui;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseListener;
import java.util.List;
import javax.swing.*;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import sk.jp.commons.exception.ApplicationException;
import sk.jp.commons.tools.MemoryState;
import sk.jp.commons.ui.ExceptionDialog;
import sk.jp.commons.ui.InfoDialog;
import sk.jp.commons.ui.MemoryIndicator;
import sk.jp.commons.ui.QuestionDialog;
import sk.jp.commons.ui.StatusBar;
import sk.jp.commons.ui.TextDialog;
import sk.jp.commons.ui.WarningDialog;
import sk.jp.commons.ui.bgproc.BackgroundProcessIndicator;
import sk.jp.commons.ui.bgproc.ProcessFailureHandler;
import sk.jp.commons.ui.bgproc.ProcessStateHandlerForStatusbar;
import sk.jp.commons.utils.BackgroundProcess;
import sk.jp.commons.utils.ExceptionUtils;
import sk.jp.commons.utils.LogMsg;
import sk.jp.jitag.ApplicationLogic;
import sk.jp.jitag.ConfigManager;
import sk.jp.jitag.SearchService;
import sk.jp.jitag.UiManager;
import sk.jp.jitag.model.config.Tool;
import sk.jp.jitag.proc.RenderThumbnailsProcess;
import sk.jp.jitag.proc.RepositoryRefreshProcess;
import sk.jp.jitag.ui.common.IconLoader;
import sk.jp.jitag.ui.common.Icons;
import sk.jp.jitag.util.Msg;
import sk.jp.jitag.util.Property;
import sk.jp.jitag.vo.SearchInput;

/**
 * Main window of application UI.
 */
@org.springframework.stereotype.Component
public class MainWindow extends JFrame implements ActionListener {

    private static final Logger log = LoggerFactory.getLogger(MainWindow.class);

    private enum ActionCode {
        menuExit,
        menuRefreshRepNow,
        menuZoom,
        menuSetRating,
        menuOpenWithTool,
        menuRotateLeft,
        menuRotateRight,
        menuEditMode,
        menuSearchMode,
        menuOpenProperties,
        menuSaveAsConfProfile,
        menuLoadConfProfile,
        menuOpenTagManager,
        menuOpenHelp,
        menuSystemInfo,
        menuOpenAbout,

        runGarbageCollector,
        ;
    }

    @Autowired
    private ConfigManager configManager;

    @Autowired
    private UiManager uiManager;

    @Autowired
    private FileThumbnailsPanel fileThumbnailsPanel;

    @Autowired
    private FileListPanel fileListPanel;

    @Autowired
    private ApplicationLogic applicationLogic;

    @Autowired
    private SearchService searchService;

    private JMenu menuImage = null;
    private JSplitPane splitPane = null;
    private EditModePanel editPanel = null;
    private JPanel searchPanel = null;

    private StatusBar statusBar = null;
    private BackgroundProcessIndicator backgroundRepoRefreshIndicator = null;
    private BackgroundProcessIndicator backgroundProcessIndicator = null;

    private JButton toolBarRotateLeft = null;
    private JButton toolBarRotateRight = null;

    //----------------------------------------------------------------------

    public MainWindow() {
        super(Msg.mainWndTitle.value());
        setIconImage(IconLoader.loadIcon(Icons.jitagIcon16).getImage());
    }

    //----------------------------------------------------------------------

    public void open(){
        init();        
        pack();
        postPack();
        statusBar.setMessage(Msg.stBarAppReadyWork.value());//Application ready to work...
    }

    //----------------------------------------------------------------------

    private void init(){
        setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
        initMenu();

        setLayout(new BorderLayout());
        initToolbar();
        initContent();
    }

    private void postPack(){
        Rectangle windowPosition = configManager.getConfig().getCurrentProfile().getWindowPosition();
        if(windowPosition == null){
            Toolkit toolkit = Toolkit.getDefaultToolkit();
            Dimension sd = toolkit.getScreenSize();
            Dimension dd = new Dimension(Property.defaultMainWndWidth.intValue(),
                                         Property.defaultMainWndHeight.intValue());
            windowPosition = new Rectangle((sd.width - dd.width) / 2,
                                           (sd.height - dd.height) / 2,
                                           Property.defaultMainWndWidth.intValue(),
                                           Property.defaultMainWndHeight.intValue());
        }

        setSize(windowPosition.width, windowPosition.height);
        setLocation(windowPosition.x, windowPosition.y);
        setVisible(true);

        int splitWidth = windowPosition.width / 4 < 250 ? windowPosition.width / 4 : 250;
        splitPane.setDividerLocation(splitWidth);
    }

    @Override
    public void setVisible(boolean visible) {
        if(!visible){
            if(!processAction(ActionCode.menuExit.name())){
                return;
            }
        }
        super.setVisible(visible);
    }

    private void initMenu(){
        JMenuBar menuBar = new JMenuBar();
        setJMenuBar(menuBar);
        JMenu menu;

        menu = new JMenu(Msg.menuFile.value());
        menuBar.add(menu);

        menu.add(createMenuItem(Icons.refreshReposNow, Msg.menuRefreshRepNow, ActionCode.menuRefreshRepNow));
        menu.addSeparator();
        menu.add(createMenuItem(Icons.exit, Msg.menuExit, ActionCode.menuExit));

        menu = new JMenu(Msg.menuImage.value());
        menuBar.add(menu);
        menuImage = menu;

        menu.add(createMenuItem(Icons.zoomImage, Msg.menuZoomImage, ActionCode.menuZoom));
        menu.addSeparator();
        JMenu ratingSubmenu = new JMenu(Msg.menuSetRating.value());
        menu.add(ratingSubmenu);
        ratingSubmenu.add(createMenuItem(Icons.ratingPlus2, " ", ActionCode.menuSetRating.name()));
        ratingSubmenu.add(createMenuItem(Icons.ratingPlus1, " ", ActionCode.menuSetRating.name()));
        ratingSubmenu.add(createMenuItem(Icons.ratingNeutral, " ", ActionCode.menuSetRating.name()));
        ratingSubmenu.add(createMenuItem(Icons.ratingMinus1, " ", ActionCode.menuSetRating.name()));
        ratingSubmenu.add(createMenuItem(Icons.ratingMinus2, " ", ActionCode.menuSetRating.name()));
        menu.add(createMenuItem(Icons.rotateLeft, Msg.menuRotateLeft, ActionCode.menuRotateLeft));
        menu.add(createMenuItem(Icons.rotateRight, Msg.menuRotateRight, ActionCode.menuRotateRight));
        JMenu toolsSubmenu = new JMenu(Msg.menuOpenWithTool.value());
        menu.add(toolsSubmenu);
        List<Tool> tools = configManager.getConfig().getCurrentProfile().getTools();
        for (Tool tool : tools) {
            toolsSubmenu.add(createMenuItem(null, tool.getLabel(),
                                         ActionCode.menuOpenWithTool.name()+"$"+tool.getLabel()));
        }

        menu = new JMenu(Msg.menuView.value());
        menuBar.add(menu);

        menu.add(createMenuItem(Icons.editMode, Msg.menuEditMode, ActionCode.menuEditMode));
        menu.add(createMenuItem(Icons.searchMode, Msg.menuSearchMode, ActionCode.menuSearchMode));

        menu = new JMenu(Msg.menuOptions.value());
        menuBar.add(menu);

        menu.add(createMenuItem(Icons.editProperties, Msg.menuOpenProperties, ActionCode.menuOpenProperties));
        menu.add(createMenuItem(Icons.saveProfile, Msg.menuSaveAsProperties, ActionCode.menuSaveAsConfProfile));
        menu.add(createMenuItem(Icons.loadProfile, Msg.menuLoadProperties, ActionCode.menuLoadConfProfile));
        menu.add(createMenuItem(Icons.tagsManager, Msg.menuOpenTagManager, ActionCode.menuOpenTagManager));

        menu = new JMenu(Msg.menuHelp.value());
        menuBar.add(menu);

        menu.add(createMenuItem(Icons.helpContent, Msg.menuHelpContent, ActionCode.menuOpenHelp));
        menu.add(createMenuItem(Msg.menuSystemInfo, ActionCode.menuSystemInfo));
        menu.add(createMenuItem(Icons.jitagIcon16, Msg.menuAbout, ActionCode.menuOpenAbout));
    }

    private void initToolbar(){
        JToolBar toolBar = new JToolBar();
        add(toolBar, BorderLayout.PAGE_START);
        toolBar.setFloatable(false);
        toolBar.setRollover(true);

        toolBarRotateLeft = createImgButton(Icons.rotateLeftBig, ActionCode.menuRotateLeft);
        toolBar.add(toolBarRotateLeft);
        toolBarRotateRight = createImgButton(Icons.rotateRightBig, ActionCode.menuRotateRight);
        toolBar.add(toolBarRotateRight);

        //TODO Dokoncit!!!
    }

    private void initContent(){
        splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
        add(splitPane, BorderLayout.CENTER);

        editPanel = new EditModePanel();
        splitPane.setLeftComponent(editPanel);

//        fileThumbnailsPanel = new FileThumbnailsPanel();
        splitPane.setRightComponent(fileThumbnailsPanel);

        searchPanel = new JPanel();//TODO Dokoncit!!!

        //Add status bar to bottom of window
        statusBar = new StatusBar();
        statusBar.setAlignmentY(Component.BOTTOM_ALIGNMENT);

        JComponent memIndicator = new MemoryIndicator(1000, MemoryIndicator.Show.usedMem,
                            MemoryIndicator.Type.textMb,
                            new MemoryIndicator.RunGcClickHandler(){
                                @Override
                                protected void onClick() {
                                    log.trace("MemoryIndicator.onClick()");
                                    processAction(ActionCode.runGarbageCollector.name());
                                }
                            });

        backgroundProcessIndicator = new BackgroundProcessIndicator(
                                            IconLoader.loadIcon(Icons.working),
                                            IconLoader.loadIcon(Icons.cancelling),
                                            IconLoader.loadIcon(Icons.idle));

        backgroundRepoRefreshIndicator = new BackgroundProcessIndicator(
                                            IconLoader.loadIcon(Icons.srWorking),
                                            IconLoader.loadIcon(Icons.srCancelling),
                                            IconLoader.loadIcon(Icons.srIdle));

        statusBar.setControls(backgroundProcessIndicator, backgroundRepoRefreshIndicator,
                              StatusBar.createSplitter(),
                              memIndicator);
        add(statusBar, BorderLayout.PAGE_END);
    }

    public void actionPerformed(ActionEvent event) {
        processAction(event.getActionCommand());
    }

    /**
     * Performs specified action command.
     * @param command see {@link sk.jp.jitag.ui.MainWindow.ActionCode}.
     * @return true if action was performed successfully, false if
     * any exception occurrs or if specified command is not supported.
     */
    private boolean processAction(String command){
        try {
            log.debug(LogMsg.inp("command", command));
            if(ActionCode.menuExit.name().equals(command)){
                actionExit();
            }else if(ActionCode.menuRefreshRepNow.name().equals(command)){
                actionRefreshRepositoryNow();
            }else if(ActionCode.menuZoom.name().equals(command)){
                actionZoomImage();
            }else if(command.startsWith(ActionCode.menuOpenWithTool.name())){
                String toolLabel = command.substring(command.indexOf('$')+1);
                actionOpenWithTool(toolLabel);
            }else if(ActionCode.menuOpenProperties.name().equals(command)){
                actionOpenPreferences();
            }else if(ActionCode.menuEditMode.name().equals(command)){
                actionEditMode();
            }else if(ActionCode.menuSearchMode.name().equals(command)){
                actionSearchMode();
            }else if(ActionCode.menuSystemInfo.name().equals(command)){
                actionSystemInfo();
            }else if(ActionCode.runGarbageCollector.name().equals(command)){
                actionRunGarbageCollector();
            }else if(ActionCode.menuOpenAbout.name().equals(command)){
                actionOpenAbout();
            }else{
                log.warn(LogMsg.toString("Unknown action command", "command", command));
                return false;
            }
            return true;
        } catch (Throwable e) {
            //Wrap common exceptions to i18supporteble exception
            if(e instanceof OutOfMemoryError){
                MemoryState ms = MemoryState.get();
                long currMaxMem = ms.convert(ms.totalMaxMemory, MemoryState.MBYTES);
                e = new ApplicationException("Insufficient memory. Try to run JVM with larger size of memory heap.",
                                             null,
                                             ExceptionUtils.paramsToMap("currMaxMem", currMaxMem),
                                             e);
            }
            //
            log.error("Unexpected exception", e);
            try {
                ExceptionDialog.show(this, e);
            } catch (Throwable e1) {
                log.error("Unexpected exception during show error message dialog", e1);
            }
            return false;
        }
    }

    private JMenuItem createMenuItem(Msg label, ActionCode actionCode){
        return createMenuItem(null, label.value(), actionCode.name());
    }

    private JMenuItem createMenuItem(Icons icon, Msg label, ActionCode actionCode){
        return createMenuItem(icon, label.value(), actionCode.name());
    }

    private JMenuItem createMenuItem(Icons icon, String label, String actionCode){
        JMenuItem mitem;
        if(icon != null && label != null){
            mitem = new JMenuItem(label, IconLoader.loadIcon(icon));
        }else if(icon != null){
            mitem = new JMenuItem(IconLoader.loadIcon(icon));
        }else if(label != null){
            mitem = new JMenuItem(label);
        }else{
            mitem = new JMenuItem();
        }
        mitem.addActionListener(this);
        mitem.setActionCommand(actionCode);
        return mitem;
    }

    private void actionExit(){
        statusBar.setMessage(Msg.stBarAppExiting.value()); //Exiting application...
        //Update properties
        Dimension size = getSize();
        Point location = getLocation();
        Rectangle windowPosition = new Rectangle(location.x, location.y, size.width, size.height);
        configManager.getConfig().getCurrentProfile().setWindowPosition(windowPosition);
        applicationLogic.exitApplication(this);
    }

    private void actionRefreshRepositoryNow(){
        //Check if currently run scan process on bacground
        if(backgroundRepoRefreshIndicator.isProcessRunning()){
            new WarningDialog().show(this, Msg.msgRescanReposRunning.value());
            return;
        }

        //Check if any repo is configured. if not, show msg, end.
        List<String> repositoryLocations = configManager.getConfig().getCurrentProfile()
            .getRepositoryLocations();
        if(repositoryLocations.isEmpty()){
            new WarningDialog().show(this, Msg.msgNoRepoRoots.value());
            return;
        }

        //Question
        QuestionDialog dialog = new QuestionDialog();
        String r = dialog.show(this, Msg.msgRescanReposNow.value()); //Do you want to rescan repositories now?

        //
        if(r.equals(QuestionDialog.Msg.btnOk.value())){
            BackgroundProcess.StateHandler stateHandler
                = new ProcessStateHandlerForStatusbar(statusBar);
            BackgroundProcess backgroundProcess
                = new RepositoryRefreshProcess(applicationLogic,
                                               backgroundRepoRefreshIndicator, stateHandler,
                                               new ProcessFailureHandler());
            MouseListener bpiClickHandler = new BackgroundProcessIndicator.DefaultClickHandler(
                                                    backgroundProcess, backgroundRepoRefreshIndicator);
            backgroundRepoRefreshIndicator.start(backgroundProcess, bpiClickHandler);
        }
    }

    private void actionZoomImage(){
        //TODO Dokoncit!!!
        statusBar.setMessage("Image zoomed");//TODO test
    }

    private void actionOpenWithTool(String toolLabel){
        log.debug(LogMsg.inp("toolLabel", toolLabel));

        List<Tool> tools = configManager.getConfig().getCurrentProfile().getTools();
        Tool toolForOpen = null;
        for (Tool tool : tools) {
            if(tool.getLabel().equals(toolLabel)){
                toolForOpen = tool;
                break;
            }
        }
        if(toolForOpen != null){
            //TODO Dokoncit!!!
        }

    }

    private void actionOpenPreferences(){
        String prevLafClass = configManager.getConfig().getCurrentProfile().getLafClass();
        String prevLafTheme = configManager.getConfig().getCurrentProfile().getLafTheme();

        boolean confChanged = applicationLogic.openPreferencesDialog(this);
        if(confChanged){
            //Refresh Look and Feel
            String newLafClass = configManager.getConfig().getCurrentProfile().getLafClass();
            String newLafTheme = configManager.getConfig().getCurrentProfile().getLafTheme();
            log.trace(LogMsg.toString("After change configuration",
                                      "prevLafClass", prevLafClass, "newLafClass", newLafClass,
                                      "prevLafTheme", prevLafTheme, "newLafTheme", newLafTheme));
            if(!prevLafClass.equals(newLafClass)
                || prevLafTheme != null && !prevLafTheme.equals(newLafTheme)
                || newLafTheme != null && !newLafTheme.equals(prevLafTheme)){
                uiManager.setupLookAndFeel(configManager.getConfig().getCurrentProfile());
                SwingUtilities.updateComponentTreeUI(this);
                pack();
                postPack();
            }

            //TODO Dokoncit!!! If repo locations changed, performs refeesh

            new InfoDialog().show(this, Msg.msgRestartAppForApply.value()); //You must restart application to apply all settings.
            statusBar.setMessage(Msg.msgRestartAppForApply.value()); //You must restart application to apply all settings.
        }
    }

    private void actionEditMode(){
        //TODO Dokoncit!!!

        //TODO test
        BackgroundProcess.StateHandler stateHandler
            = new ProcessStateHandlerForStatusbar(statusBar);
        BackgroundProcess backgroundProcess
            = new RenderThumbnailsProcess(backgroundProcessIndicator, stateHandler,
                                          new ProcessFailureHandler());
        MouseListener bpiClickHandler = new BackgroundProcessIndicator.DefaultClickHandler(
                                                backgroundProcess, backgroundProcessIndicator);
        backgroundProcessIndicator.start(backgroundProcess, bpiClickHandler);
        //\t
    }

    private void actionSearchMode(){
        //TODO Dokoncit!!!


        //TODO test - show all images in repos
        searchService.search(new SearchInput());
        searchService.testSetup();
    }

    private void actionSystemInfo(){
        String systemInfoText = applicationLogic.buildSystemInfo();

        TextDialog dialog = new TextDialog(this, Msg.appSysPropsDialogTitle.value(), true); //Configuration and system properties
        dialog.setWidth(800);
        dialog.setHeight(600);
        dialog.open(systemInfoText);
    }

    private void actionRunGarbageCollector(){
        applicationLogic.runGarbageCollector(backgroundProcessIndicator, statusBar);
    }

//    /**
//     * Performs UI changes when any image(s) has been selected or unselected.
//     * @param selected true if selected, false if unselected.
//     */
//    public void onImageSelected(boolean selected){
//        menuImage.setEnabled(selected);
//        toolBarRotateLeft.setEnabled(selected);
//        toolBarRotateRight.setEnabled(selected);
//    }

    private JButton createImgButton(Icons icon, ActionCode actionCode){
        ImageIcon imageIcon = IconLoader.loadIcon(icon);
        JButton button = new JButton(imageIcon);
        button.setPreferredSize(new Dimension(imageIcon.getIconWidth()+4,
                                              imageIcon.getIconHeight()+4));
        button.setFocusable(false);
        button.setActionCommand(actionCode.name());
        button.addActionListener(this);
        return button;
    }

    private void actionOpenAbout(){
        AboutDialog.show(this);
    }

}
